package com.cmc.cloud.cmclink.doc.service.impl.approvespace;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.cmc.cloud.cmclink.doc.api.document.emums.*;
import com.cmc.cloud.cmclink.doc.constants.CommonConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SoApproveSpaceErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.constants.errorconstant.SoErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.convert.ApproveSpaceConvert;
import com.cmc.cloud.cmclink.doc.dto.RestoreSpace;
import com.cmc.cloud.cmclink.doc.entity.SoCargoDO;
import com.cmc.cloud.cmclink.doc.entity.SoDO;
import com.cmc.cloud.cmclink.doc.entity.SoVslvoyDO;
import com.cmc.cloud.cmclink.doc.entity.SpecialCargoApplyDO;
import com.cmc.cloud.cmclink.doc.mapper.SoMapper;
import com.cmc.cloud.cmclink.doc.mapper.SoVslvoyMapper;
import com.cmc.cloud.cmclink.doc.service.SpecialCargoApplyService;
import com.cmc.cloud.cmclink.doc.service.VesselOffHireMaintenanceService;
import com.cmc.cloud.cmclink.doc.service.approvespace.ApproveSpaceService;
import com.cmc.cloud.cmclink.doc.service.approvespace.AutoSpaceService;
import com.cmc.cloud.cmclink.doc.service.so.*;
import com.cmc.cloud.cmclink.doc.vo.sovo.approvespacevo.SoApproveSpaceReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.approvespacevo.SoApproveSpaceResVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.approvespacevo.SoRejectSpaceReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.approvespacevo.UpdateBkgVesselReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.operatelog.OperateLogCreateReqVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socargovo.SoCargoBaseVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoDetailRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoRespVO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.cmes.framework.common.pojo.PageResult;
import com.cmes.framework.security.core.util.SecurityFrameworkUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.tika.utils.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lzp
 */
@Service
@Validated
@Slf4j
public class ApproveSpaceServiceImpl implements ApproveSpaceService {

    @Resource
    private SoMapper soMapper;

    @Resource
    private SoVslvoyMapper soVslvoyMapper;

    @Resource
    private SoService soService;

    @Resource
    private SoVslvoyService soVslvoyService;

    @Resource
    private SpecialCargoApplyService specialCargoApplyService;

    @Resource
    private BizSoService bizSoService;

    @Resource
    private SoCargoService soCargoService;

    @Resource
    private OperateLogService operateLogService;
    @Resource
    private SoCommonService soCommonService;

    @Resource
    private AutoSpaceService autoSpaceService;
    @Resource
    VesselOffHireMaintenanceService vesselOffHireMaintenanceService;

    @Override
    public PageResult<SoApproveSpaceResVO> searchSoApproveSpacePage(SoApproveSpaceReqVO soApproveSpaceReqVO) {
        validSearchReqParam(soApproveSpaceReqVO);
        dealWithDraftSoStatus(soApproveSpaceReqVO);
        PageResult<SoRespVO> result = bizSoService.getSoPage(soApproveSpaceReqVO, true);
        List<SoApproveSpaceResVO> soApproveSpaceResVOList = ApproveSpaceConvert.INSTANCE.convertList(result.getList());
        List<String> bookingNos = soApproveSpaceResVOList.stream().map(SoApproveSpaceResVO::getBookingNo)
                .filter(bookingNo -> !StringUtils.isEmpty(bookingNo)).collect(Collectors.toList());
        List<SpecialCargoApplyDO> specialDOList = specialCargoApplyService.getByBookingNos(bookingNos);
        soApproveSpaceResVOList.forEach(vo -> {
            // 更新特种货状态
            specialDOList.forEach(sdo -> {
                if (sdo.getBookingNo().equals(vo.getBookingNo())) {
                    vo.setSpecialCargoStatus(sdo.getApplyStatus());
                }
            });
        });

        return new PageResult<>(soApproveSpaceResVOList, result.getTotal());
    }

    private void dealWithDraftSoStatus(SoApproveSpaceReqVO soApproveSpaceReqVO) {
        List<String> enumValues = new ArrayList<>();
        if (soApproveSpaceReqVO.getSoStatusList() == null || soApproveSpaceReqVO.getSoStatusList().length == 0) {
            for (SoStatusEnum soEnum : SoStatusEnum.values()) {
                if (!soEnum.getValue().equals(SoStatusEnum.BookingDraft.getValue())) {
                    enumValues.add(soEnum.getValue());
                }
            }
            soApproveSpaceReqVO.setSoStatusList(enumValues.toArray(new String[0]));
        }
    }

    private void validSearchReqParam(SoApproveSpaceReqVO soApproveSpaceReqVO) {
        if (StringUtils.isEmpty(soApproveSpaceReqVO.getBookingVessel())
            && StringUtils.isEmpty(soApproveSpaceReqVO.getBookingVoyage())
            && StringUtils.isEmpty(soApproveSpaceReqVO.getPolCode())) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_VESSEL_EMPTY);
        }
    }

    @Override
    public SoDetailRespVO getDetail(Long id) {
        return bizSoService.getDetail(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBkgVessel(UpdateBkgVesselReqVO updateBkgVesselReqVO) {
        List<Long> ids = updateBkgVesselReqVO.getIds();
        List<SoDO> soDOList = soService.getSoList(ids);
        // 批量操作只有 POL、BKGVessel&Voyage 一样的记录才能批量改配操作;
        // 对于 SO Status 是Booking Confirmed、Booking Pending for Space 的记录可以进行改配操作
        validBatchParam(soDOList);
        checkMaintenanceDateBySoIds(ids, Arrays.asList(updateBkgVesselReqVO.getVesselCode()));
        // 归还分配的teu及rf
        restoreAllocation(ids);

        // 修改保存数据库
        saveToDb(soDOList, updateBkgVesselReqVO, ids);

        // 发通知给箱管
        sendMessageToContainer();
    }

    private void restoreAllocation(List<Long> ids) {
        List<SoVslvoyDO> soVslvoyList = soVslvoyService.selectFirstLinerBySoIds(ids);
        List<Long> returnIdList = new ArrayList<>(8);
        List<RestoreSpace> restoreSpaceList = new ArrayList<>(8);
        soVslvoyList.forEach(e -> {
            if (e.getAllocationId() != null) {
                returnIdList.add(e.getId());
                restoreSpaceList.add(new RestoreSpace(e.getSoId(), Arrays.asList(e.getAllocationId())));
            }
        });
        // 同步自动批舱 重置分配
        if (CollectionUtil.isNotEmpty(returnIdList)) {
            soVslvoyService.setAllocationIdNull(returnIdList);
            autoSpaceService.restoreAllocatedSpace(restoreSpaceList);
        }
    }

    private void validBatchParam(List<SoDO> soDOList) {
        if (CollectionUtils.isEmpty(soDOList)) {
            throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_NOT_EXISTS);
        }
        SoDO soDoFirst = soDOList.get(0);
        String pol = soDoFirst.getPolCode();
        String vessel = soDoFirst.getBookingVessel();
        String voyage = soDoFirst.getBookingVoyage();
        soDOList.forEach(so -> {
            if (!so.getPolCode().equals(pol) || !so.getBookingVessel().equals(vessel)
                || !so.getBookingVoyage().equals(voyage)) {
                throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_CHANGE_BKG_NOT_CONSISTENT);
            }

            String status = so.getSoStatus();
            if (!SoStatusEnum.BookingConfirmed.getValue().equals(status)
                && !SoStatusEnum.BookingPendingSpace.getValue().equals(status)) {
                throw ServiceExceptionUtil.exception(SoErrorCodeConstants.SO_CHANGE_BKG_STATUS_NOT_REQUIRED);
            }
        });
    }

    private void saveToDb(List<SoDO> soDOList, UpdateBkgVesselReqVO updateBkgVesselReqVO, List<Long> ids) {
        soDOList.forEach(so -> {
            so.setUpdater(String.valueOf(Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser()).getId()));
            so.setUpdateTime(LocalDateTime.now());
            so.setBookingVoyage(updateBkgVesselReqVO.getVoyageEx());
            so.setBookingVessel(updateBkgVesselReqVO.getVesselCode());
        });
        soMapper.updateBatch(soDOList);

        // 修改第一条船名航次
        Map<Long, List<SoVslvoyDO>> soVslvoyListMap = soVslvoyService.seleteBySoIdsMap(ids);
        List<SoVslvoyDO> soVslvoyDbList = new ArrayList<>();
        soVslvoyListMap.forEach((soId, soVslvoyList) -> {
            // 查找第一条航线
            Optional<SoVslvoyDO> soVslvoyOpt =
                soVslvoyList.stream().min(Comparator.comparingInt(SoVslvoyDO::getRouteNo));
            soVslvoyOpt.ifPresent(soVslvoyFirst -> {
                soVslvoyFirst.setLaneCodeEx(updateBkgVesselReqVO.getLaneCodeEx());
                soVslvoyFirst.setVesselCode(updateBkgVesselReqVO.getVesselCode());
                soVslvoyFirst.setVoyageEx(updateBkgVesselReqVO.getVoyageEx());
                soVslvoyDbList.add(soVslvoyFirst);
            });
        });
        try {
            soVslvoyMapper.updateBatch(soVslvoyDbList);
        } catch (Exception e) {
            log.error("caught an exception on ApproveSpaceServiceImpl.saveToDb", e);
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_BKG_BATCH_UPDATE_REPEAT);
        }
    }

    private void sendMessageToContainer() {
        // 消息格式待定
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveSpace(Long id) {
        SoVslvoyDO soVslvoyDO = soVslvoyService.getSoVslvoy(id);
        validAllowApprove(soVslvoyDO);
        checkMaintenanceDateBySoIds(Arrays.asList(soVslvoyDO.getSoId()), Arrays.asList(soVslvoyDO.getVesselCode()));
        SoDO soDO = soService.getSo(soVslvoyDO.getSoId());
        String transMode = soVslvoyDO.getTransmode();
        // 仅当运输方式Liner需要批舱
        if (!TransmodeEnum.LINER.getValue().equals(transMode)) {
            return;
        }
        validApproveSoStatus(soDO);
        soVslvoyDO.setSpaceApproveStatus(ApproveStatusEnum.APPROVED.getValue());
        soVslvoyMapper.updateById(soVslvoyDO);

        soCommonService.syncSoStatus(soDO.getId(), ApproveStatusEnum.APPROVED.getValue());

        // 处理pending(订舱舱位确认中)状态
        dealWithPendingStatus(soVslvoyDO.getSoId());

        // 如果是空调箱调运订舱，需要发通知给箱管
        if (CntrOwnerEnum.SNL.getValue().equals(soDO.getCntrOwner())
            && CntrStatusEnum.E.getValue().equals(soDO.getCntrStatus())) {
            dealWithEmptyCntr();
        }
    }

    private void dealWithPendingStatus(Long soId) {
        SoDO soDO = soService.getSo(soId);
        if (SoStatusEnum.BookingPendingSpace.getValue().equals(soDO.getSoStatus()) && checkAllVslvoyNoPending(soId)) {
            Long operateLogId = saveOperateLog(soDO);
            soDO.setSoStatus(SoStatusEnum.BookingProcessing.getValue());
            soDO.setOperateLogId(operateLogId);
            soService.updateById(soDO);
        }
    }

    private void validAllowApprove(SoVslvoyDO soVslvoyDO) {
        String approveStatus = soVslvoyDO.getSpaceApproveStatus();
        if (ApproveStatusEnum.APPROVED.getValue().equalsIgnoreCase(approveStatus)
                || ApproveStatusEnum.REJECTED.getValue().equalsIgnoreCase(approveStatus)
        ) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_APPROVE_OR_REJECT_NOT_ALLOWED);
        }

        Boolean inAutoApproveTrack = soVslvoyDO.getInAutoApproveTrack();
        if (inAutoApproveTrack != null && inAutoApproveTrack) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_APPROVE_AUTO_PROCESSING);
        }
    }

    private void validAllowApprovePending(SoVslvoyDO soVslvoyDO) {
        if (ApproveStatusEnum.PENDING_ON_SPACE.getValue().equalsIgnoreCase(soVslvoyDO.getSpaceApproveStatus())) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_REPEATED_PENDING_ON_SPACE);
        }
        validAllowApprove(soVslvoyDO);
    }

    /**
     * 判断so是否已经特种货审批
     *
     * @param soDO SO对象
     * @return boolean
     */
    public boolean checkApproveSpecialCargo(SoDO soDO) {
        // 非特种货直接返回true
        boolean checkSpecial = checkSpecialCargo(soDO.getId());
        if (!checkSpecial) {
            return true;
        }
        String bookingNo = soDO.getBookingNo();
        if (StringUtils.isEmpty(bookingNo)) {
            return false;
        }
        SpecialCargoApplyDO specialCargoApplyDO = specialCargoApplyService.getByBookingNo(soDO.getBookingNo());
        return specialCargoApplyDO != null
            && ApplyStatusEnum.APPROVED.getValue().equalsIgnoreCase(specialCargoApplyDO.getApplyStatus());
    }

    /**
     * 判断是否是特种货
     *
     * @param soId SO主键
     * @return boolean
     */
    public boolean checkSpecialCargo(Long soId) {
        List<SoCargoDO> soCargoDOList = soCargoService.seleteBySoId(soId);
        if (CollectionUtils.isEmpty(soCargoDOList)) {
            return false;
        }
        // 现需求每个SOID最多对应一种特种货
        return SoCargoDO.checkSpecialCargo(soCargoDOList.get(0));
    }

    private void validApproveSoStatus(SoDO soDO) {
        String soStatus = soDO.getSoStatus();
        if (!SoStatusEnum.BookingProcessing.getValue().equals(soStatus)
                && !SoStatusEnum.BookingPendingSpace.getValue().equals(soStatus)
                && !SoStatusEnum.CustomerUploadedMaterial.getValue().equals(soStatus)) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_STATUS_NOT_SATISFIABLE);
        }
    }

    private boolean validApproveSoContent(SoDO soDO) {
        return ApproveStatusEnum.APPROVED.getValue().equals(soDO.getSoContentStatus());
    }

    private void updateApproveStatus(SoVslvoyDO soVslvoyDO, String status, SoDO soDO, boolean specialApprove) {
        soVslvoyDO.setSpaceApproveStatus(status);
        soVslvoyMapper.updateById(soVslvoyDO);

        boolean allApprove = checkAllVslvoyApprove(soVslvoyDO.getSoId());
        if (allApprove && specialApprove) {
            Long operateLogId = saveOperateLog(soDO);
            soDO.setSoStatus(SoStatusEnum.BookingConfirmed.getValue());
            soDO.setOperateLogId(operateLogId);
            soService.updateById(soDO);
        } else {
            if (SoStatusEnum.BookingPendingSpace.getValue().equals(soDO.getSoStatus())
                && checkAllVslvoyNoPending(soVslvoyDO.getSoId())) {
                Long operateLogId = saveOperateLog(soDO);
                soDO.setSoStatus(SoStatusEnum.BookingProcessing.getValue());
                soDO.setOperateLogId(operateLogId);
                soService.updateById(soDO);
            }
        }
    }

    private void updateApproveStatus(SoVslvoyDO soVslvoyDO, String status, SoDO soDO) {
        updateApproveStatus(soVslvoyDO, status, soDO, true);
    }

    private boolean checkAllVslvoyApprove(Long soId) {
        List<SoVslvoyDO> soVslvoyDOList = soVslvoyService.seleteBySoId(soId);
        for (SoVslvoyDO soVslvoyTmp : soVslvoyDOList) {
            if (!ApproveStatusEnum.APPROVED.getValue().equals(soVslvoyTmp.getSpaceApproveStatus())) {
                return false;
            }
        }
        return true;
    }

    private boolean checkAllVslvoyNoPending(Long soId) {
        List<SoVslvoyDO> soVslvoyDOList = soVslvoyService.seleteBySoId(soId);
        for (SoVslvoyDO soVslvoyTmp : soVslvoyDOList) {
            if (ApproveStatusEnum.PENDING_ON_SPACE.getValue().equals(soVslvoyTmp.getSpaceApproveStatus())) {
                return false;
            }
        }
        return true;
    }

    private void dealWithEmptyCntr() {}

    @Override
    public void rejectSpace(Long id, SoRejectSpaceReqVO soRejectSpaceReqVO) {
        // so status 为Booking Processing或者Booking Pending for Space才能进行这个操作
        SoVslvoyDO soVslvoyDO = soVslvoyService.getSoVslvoy(id);
        validAllowApprove(soVslvoyDO);
        SoDO soDO = soService.getSo(soVslvoyDO.getSoId());
        validApproveSoStatus(soDO);
        Long operateId = saveOperateLog(soDO, CommonConstants.OPERATE_TYPE_REJECT, soRejectSpaceReqVO.getReasonType(),
            soRejectSpaceReqVO.getReasonContent());
        soDO.setOperateLogId(operateId);
        soVslvoyDO.setSpaceApproveStatus(ApproveStatusEnum.REJECTED.getValue());
        soVslvoyMapper.updateById(soVslvoyDO);
        soService.updateById(soDO);
        soCommonService.syncSoStatus(soDO.getId(), ApproveStatusEnum.REJECTED.getValue());
    }

    @Override
    public void pendingSpace(Long id, SoRejectSpaceReqVO soRejectSpaceReqVO) {
        // so status 为Booking Processing才能进行这个操作
        SoVslvoyDO soVslvoyDO = soVslvoyService.getSoVslvoy(id);
        validAllowApprovePending(soVslvoyDO);
        SoDO soDO = soService.getSo(soVslvoyDO.getSoId());
        String status = soDO.getSoStatus();
        if (!SoStatusEnum.BookingProcessing.getValue().equals(status)
                && !SoStatusEnum.CustomerUploadedMaterial.getValue().equals(status)) {
            throw ServiceExceptionUtil.exception(SoApproveSpaceErrorCodeConstants.SO_PENDING_STATUS_NOT_SATISFIABLE);
        }
        Long operateId = saveOperateLog(soDO, CommonConstants.OPERATE_TYPE_PENDING, soRejectSpaceReqVO.getReasonType(),
                soRejectSpaceReqVO.getReasonContent());
        soDO.setSoStatus(SoStatusEnum.BookingPendingSpace.getValue());
        soDO.setOperateLogId(operateId);
        soVslvoyDO.setSpaceApproveStatus(ApproveStatusEnum.PENDING_ON_SPACE.getValue());
        soVslvoyMapper.updateById(soVslvoyDO);
        soService.updateById(soDO);
    }

    private Long saveOperateLog(SoDO soDO) {
        return saveOperateLog(soDO, CommonConstants.OPERATE_TYPE_APPROVE, null, null);
    }

    private Long saveOperateLog(SoDO soDO, String operateSubType, String reasonType, String reasonContent) {
        OperateLogCreateReqVO operateLog = new OperateLogCreateReqVO();
        operateLog.setBusinessId(soDO.getId());
        operateLog.setOperateType(OperateTypeEnum.SPACE_APPROVE.getValue());
        operateLog.setBusinessNo(soDO.getBookingNo());
        operateLog.setBusinessType(BusinessTypeEnum.SO.getValue());
        operateLog.setOperateTypeSub(operateSubType);
        operateLog.setOperateTypeThird(reasonType);
        operateLog.setOperateDesc(reasonContent);
        return operateLogService.createOperateLog(operateLog);
    }

    public void checkMaintenanceDateBySoIds(List<Long> soIds, List<String> vesselCodeList) {
        Map<Long, SoCargoDO> soCargoMap = soCargoService.selectBySoIds(new ArrayList<Long>(soIds));
        soIds.forEach(e -> {
            SoCargoDO soCargo = soCargoMap.get(e);
            checkVesselOffHireMaintenanceDate(BeanUtil.copyProperties(soCargo, SoCargoBaseVO.class), vesselCodeList);
        });
    }

    public void checkVesselOffHireMaintenanceDate(SoCargoBaseVO cargo, List<String> vesselCodeList) {
        if (cargo != null && CollectionUtil.isNotEmpty(vesselCodeList)) {
            if (SoCargoBaseVO.checkSpecialCargo(cargo.getCargoType(), cargo.getSubCargoType())) {
                //订特种货时，不允许提交禁接的船舶
                vesselOffHireMaintenanceService.checkVesselOffHireMaintenanceDate(vesselCodeList, LocalDate.now());
            }
        }
    }

}
